﻿using System;
using System.Runtime.Remoting.Messaging;
using System.Threading;

namespace Raider.WinForms.Actions.Utils
{
    static class ThreadUtils
    {
        /// <summary>
        /// Executes specified method with a single parameter asynchronously.
        /// </summary>
        /// <typeparam name="TArg">Type of the argument to pass.</typeparam>
        /// <param name="method">Method to run asynchronously.</param>
        /// <param name="completed">Action to call on initial thread when method is executed without exceptions.</param>
        /// <param name="except">Executed only in case of exception in the method.</param>
        /// <param name="arg">Arument to pass.</param>
        public static IAsyncResult ExecuteAsync<TArg>(Action<TArg> method, Action<TArg> completed, 
            Action<Exception> except, TArg arg)
        {
            var stateObj = new object[] { completed, except, arg, SynchronizationContext.Current };
            return method.BeginInvoke(arg, AsyncEventCallback<TArg>, stateObj);
        }

        private static void AsyncEventCallback<TArg>(IAsyncResult result)
        {
            // Retrieve the delegate.
            var asyncResult = (AsyncResult)result;
            var caller = (Action<TArg>)asyncResult.AsyncDelegate;

            // Call EndInvoke to retrieve the results.
            Exception exception = null;
            try
            {
                caller.EndInvoke(result);
            }
            catch (Exception ex)
            {
                exception = ex;
            }

            // Raise 'completed' event on UI thread.
            var stateObj = (object[])result.AsyncState;
            var completedEvent = (Action<TArg>)stateObj[0];
            var exceptionEvent = (Action<Exception>)stateObj[1];
            var completedEventArgs = (TArg)stateObj[2];
            var syncContext = (SynchronizationContext)stateObj[3];

            if (exception != null)
            {
                if (exceptionEvent != null)
                {
                    syncContext.Post(s => exceptionEvent((Exception)s), exception);
                }
                else
                {
                    throw exception;
                }
            }
            else
            {
                syncContext.Post(s => completedEvent((TArg)s), completedEventArgs);
            }
        }
    }
}
